#import "MultApp.h"
#import "MultDoc.h"
#import "Help.h"
#import "PrefDelegate.h"
#import "PageMargin.h"
#import <appkit/appkit.h>

@implementation MultApp:Object

/* Canon Information Systems is not responsible for anything anyone does with this   */
/* code, nor are they responsible for the correctness of this code.  Basically, this */
/* has very little to do with the company I work for, and you can't blame them.      */

/* This file is best read in a window as wide as this comment, and with tab settings */
/* of 4 spaces and indent setting of 4 spaces (at least, that's what I use).		 */

/* You are welcome to do as you would with this file under the following conditions. */
/* First, I accept no blame for anything that goes wrong no matter how you use it,   */
/* no matter how catastrophic, not even if it stems from a bug in my code.			 */
/* Second, please keep my notices on it when/if you distribute it.					 */
/* Third, if you discover any bugs or have any comments, PLEASE TELL ME!  Code won't */
/* get better without people picking it apart and giving the writer feedback.        */
/* Fourth, if you modify it, please keep a notice that your version is based on mine */
/* in the source files (and keep the notice that mine is based on four other pieces  */
/* of code :<).  Thanks, and have fun.  - Subrata Sircar, ssircar@canon.com			 */

/* This class is intended as an Application Delegate.  As such it */
/* is supposed to provide the document-management functionality   */
/* that a multiple-document, drag-and-drop application should be  */
/* able to do.  This includes keeping track of the current thing, */
/* managing the shared panels, updating the defaults and menus,   */
/* and controlling communication with the Workspace Manager.	  */

/* The paradigm used is that the application delegate relays new messages */
/* to the factory Document class, and every other doc message goes to     */
/* the first responder.  The delegate opens nib sections containing       */
/* the info and preferences panel (the prefs panel should have a delegate */
/* to update it) and lets the helpObject worry about itself.  Otherwise   */
/* it acts on delegate messages.										  */

/* Source code stolen from Draw, Acceptor, and WhatsUpDoc.  Thanks! 	  */
/* Source code also taken from Ernest Prabhakar's BasicApp example, which */
/* includes more methods than mine does (for things like services, etc.)  */
/* His examples are well worth checking out, especially if you need more  */
/* functionality than this class can provide.							  */
/* - Subrata Sircar			ssircar@canon.com	Canon Information Systems */
 
/* Version 0.9b			Apr-19-92		First Public Release	*/
/* Version 0.95b		Jun-10-92		Minor File path fixes	*/
/* Version 1.0b			Aug-10-92		Minor Bug Fixes			*/

/* Preprocessor macros */

#define DocClass		[[[self class] docClass] class]
#define DocExtension	[DocClass extension]
#define theVersion		((float)([[self class] version])/100.0)
#define marginDefault	"36.0"

/* Factory (Class) Variables */
/* These are declared as static internal because I felt that all members	*/
/* of this class would use the same values for these variables.  Subclasses	*/
/* can only manipulate these variables through the provided class methods. 	*/
/* BE VERY CAREFUL about overriding methods which depends on the class		*/
/* variables without calling the super method - it might cause problems.	*/

static id      		docClass;				/* Class of Document to use 		*/

static const char	*theAppName;			/* Cache the appName for later use	*/
static const char	*noValue;				/* Cache the local no and yes		*/
static const char	*yesValue;

static const char 	*VersionFormat;			/* Version Format String			*/
static const int 	myVersion 		= 100;	/* Version multiplied by 100		*/
BOOL 				InMsgPrint 		= NO;

/* Factory (Class) Methods */

+ initialize
/* Class variable initialization.  DO NOT call from subclasses. */
{
    if (self == [MultApp class]) {
		[self setVersion:myVersion];
		[self setDocClass:[MultDoc class]];
		VersionFormat = NXCopyStringBufferFromZone(LocalString("Version %.2f"), MyZone);
		theAppName = NXCopyStringBufferFromZone([NXApp appName],MyZone);
		noValue = NXCopyStringBufferFromZone(LocalString("NO"),MyZone);
		yesValue = NXCopyStringBufferFromZone(LocalString("YES"),MyZone);
	}
	return self;
}

+ setDocClass:newDoc
/* Sets the document class and the file extensions to be used.  Requires	*/
/* the document class to implement a class method to return the extension	*/
/* and the file types.														*/
{
    docClass = newDoc;
    return self;
}

+ docClass
{
    return docClass;
}

/* Private C functions used to implement methods in this class. */

static void initMenu(id menu)
/*
 * Sets the updateAction for every menu item which sends to the
 * First Responder (i.e. their target is nil).  When autoupdate is on,
 * every event will be followed by an update of each of the menu items
 * which is visible.  This keep all unavailable menu items dimmed out
 * so that the user knows what options are available at any given time.
 */ 
{
    int count;
    id matrix, cell;
    id matrixTarget, cellTarget;

	matrix = [menu itemList];
    matrixTarget = [matrix target];

    count = [matrix cellCount];
    while (count--) {
		cell = [matrix cellAt:count :0];
		cellTarget = [cell target];
		if (!matrixTarget && !cellTarget) {
			[cell setUpdateAction:@selector(menuItemUpdate:) forMenu:menu];
		} else if ([cell hasSubmenu]) {
			initMenu(cellTarget);
		} else if (cellTarget == [NXApp delegate])
			[cell setUpdateAction:@selector(menuItemUpdate:) forMenu:menu];
    }
}

static id documentInWindow(id window)
/*
 * Checks to see if the passed window's delegate is a document.
 * If it is, it returns that document, otherwise it returns nil.
 */
{
    id document = [window delegate];
	if (document) return [document isKindOf:docClass] ? document : nil;
	else return nil;
}

static id findDocument(const char *name)
/*
 * Searches the window list looking for a document with the specified name.
 * Returns the window containing the document if found.
 * If name == NULL then the first document found is returned.
 */
{
    int count;
    id document, window, windowList;

    windowList = [NXApp windowList];
    count = [windowList count];
    while (count--) {
		window = [windowList objectAt:count];
		document = documentInWindow(window);
		if (document && (!name || !strcmp([document fileName], name))) return window;
    }

    return nil;
}

static id openFile(const char *directory, const char *name, BOOL display)
/*
 * Opens a file with the given name in the specified directory.
 * If we already have that file open, it is ordered front.
 * Returns the document if successful, nil otherwise.
 */
{
    id window;
    char buffer[MAXPATHLEN+1], path[MAXPATHLEN+1];

    if (name && *name) {
		if (!directory) directory = ".";
		else if (*directory != '/') {
			strcpy(buffer, "./");
			strcat(buffer, directory);
			directory = buffer;
		}
		if (!chdir(directory) && getwd(path)) {
			strcat(path, "/");
			strcat(path, name);
			window = findDocument(path);
			if (window) {
				if (display) [window makeKeyAndOrderFront:window];
				return [window delegate];
	    	} else return [docClass newFromFile:path];
		} else Notify(LocalString("Open:  path invalid"), directory);
    }

    return nil;
}


/* Public Methods */

- currentDocument
{
    return documentInWindow([NXApp mainWindow]);
}

- (const char *)currentDirectory
/* Returns the current document's directory */
{
    const char *retval = [[self currentDocument] directory];
    if (!retval || !*retval) retval = defaultDir;
    return retval;
}

- setDefaultDir:(const char *)dir
{
	sprintf(defaultDir,"%s",dir);
    return self;
}

- (const char *)launchDirectory
{
    return launchDir;
}


/* Default support methods */
// These functions manage the app's behavior when it quits with unsaved documents

- (BOOL)saveAll
{
    return saveAll;
}

- setSaveAll:(BOOL)value
{
    saveAll = value;
	return self;
}

- (BOOL)dumpAll
{
    return dumpAll;
}

- setDumpAll:(BOOL)value
{
    dumpAll = value;
	return self;
}


/* Shared Panels */

- info:sender
/*
 * Returns the information Panel if it hasn't already been loaded.
 */
{
	id versionField;	/* version field in info panel */
	char buf[20];
    if (!infoPanel) {
		NXZone *zone = NXCreateChildZone(MyZone, vm_page_size, vm_page_size, YES);
		NXNameZone(zone,"infoPanel");
		LoadLocalNib(LocalString("Info.nib"),self,YES,zone);
		versionField = NXGetNamedObject("VersionNumber", infoPanel);
		if (versionField) {
			sprintf(buf, VersionFormat,theVersion);
			[versionField setStringValue:buf];
		}
		NXMergeZone(zone);
    }
	[infoPanel orderFront:self];
    return infoPanel;
}

- pref:sender
/*
 * The preferences panel is a separate nib module and only loaded on demand.
 * When loaded, the object that manages the panel is asked to update it.
 */
{
    if (!prefPanel) {
		NXZone *zone = NXCreateChildZone(MyZone, vm_page_size, vm_page_size, YES);
		LoadLocalNib(LocalString("Pref.nib"),self,NO,zone);
		NXMergeZone(zone);
    }
	[[prefPanel delegate] load:self];
	[prefPanel orderFront:self];
    return prefPanel;
}

- saveAsPanel:sender
{
    id savepanel = [SavePanel new];
	[savepanel setAccessoryView:nil];
	[savepanel setRequiredFileType:DocExtension];
    return savepanel;
}

- pageLayout:sender
/*
 * Returns the application-wide PageLayout panel, with margins.
 */
{
    return pageMargin;
}

- stringSet:sender
/*
 * Returns the application-wide string table, if it exists.
 */
{
    if (stringSet) return stringSet;
	else return nil;
}

- help:sender
{
    if (!helpPanel) {
		NXZone *zone = NXCreateChildZone(MyZone, vm_page_size, vm_page_size, YES);
		LoadLocalNib(LocalString("Help.nib"),self,NO,zone);
		NXMergeZone(zone);
    }
	[[helpPanel delegate] generalHelp:self];
	[helpPanel orderFront:self];
    return helpPanel;
}


/* Target/Action Methods */

- new:sender
/*
 * Called by pressing New in the Document menu.
 */
{
    [[docClass class] new];
    return self;
}

- open:sender
/*
 * Called by pressing Open... in the Document menu.
 */
{
    const char *directory;
    const char *const *files;
    const char *const myType[2] = {DocExtension, NULL};
    id openpanel = [[OpenPanel new] allowMultipleFiles:YES];

	directory = [self currentDirectory];
    if (directory && (*directory == '/')) [openpanel setDirectory:directory];
    if ([openpanel runModalForTypes:myType]) {
		files = [openpanel filenames];
		directory = [openpanel directory];
		while (files && *files) {
			haveOpenedDocument = openFile(directory, *files, YES) || haveOpenedDocument;
			files++;
		}
    }

    return self;
}

- saveAll:sender
/*
 * Saves all the documents.
 */
{
    int count;
    id windowList;

    windowList = [NXApp windowList];
	count = [windowList count];
    while (count--) {
		[documentInWindow([windowList objectAt:count]) save:self];
    }

    return self;
}

- print:sender
{
    return [[[self currentDocument] view] printPSCode:sender];
}

- mailToMe:sender
// Stolen from Opener 3.0.  Thanks, Michael!
{
    char subj[256], w[256] = "whoami";
    char body[4096]="\
Subrata:\n\n\
Great source code!  It runs like beauty in the night...\n\
BUT!  I do have a few comments:\n\n\
   <insert accolades, criticisms & suggestions here>\n\n\
             Sincerely,\n\
             ";
#define call(a,b) [s performRemoteMethod:a with:b length:strlen(b)+1]
    id s = [NXApp appSpeaker];
    port_t mailPort = NXPortFromName("Mail", NULL); // make sure app is launched

	mailPort = NXPortFromName("MailSendDemo",NULL);
	if (mailPort == PORT_NULL) {
		Notify(LocalString("Suggestion attempt failed with Missing Port to Mail"),LocalString("Check if Mail is running."));
		return self;
	}
    [s setSendPort:mailPort];

    sprintf(subj,"Comments and suggestions for ``%s'', Version %.2f ",theAppName,theVersion);
    strcat(body,execstr(w)); strcat(body,"\n");
    call("setTo:","ssircar@canon.com");
    call("setSubject:",subj);
    call("setBody:",body);
    
    return self;
}


/* Automatic update methods */

- (BOOL)menuItemUpdate:menuCell
/*
 * Method called by all menu items which send their actions to the
 * First Responder.  First, if the object which would respond if the
 * action was sent down the responder chain also responds to the message
 * validateCommand:, then it is sent validateCommand: to determine
 * whether that command is valid now; otherwise, if there is a responder
 * to the message, then it is assumed that the item is valid.
 * The method returns YES if the cell has changed its appearance (so that
 * the caller (a Menu) knows to redraw it).
 * This method also traps menu items sending to this class, and sets the
 * action the same way.
 */
{
    SEL action;
    id responder, target;
    BOOL enable;

    target = [menuCell target];
    enable = [menuCell isEnabled];

    if (!target) {
		action = [menuCell action];
		responder = [NXApp calcTargetForAction:action];
		if ([responder respondsTo:@selector(validateCommand:)]) {
			enable = [responder validateCommand:menuCell];
		} else enable = responder ? YES : NO;
    } else if (target == self) enable = [self validateCommand:menuCell];

    if ([menuCell isEnabled] != enable) {
		[menuCell setEnabled:enable];
		return YES;
    } else return NO;
}

- (BOOL)validateCommand:menuCell
/* The only messages the delegate is asked to validate are SaveAll and Print. */
{
    SEL action = [menuCell action];

    if ((action == @selector(saveAll:)) || (action == @selector(print:))) return findDocument(NULL) ? YES : NO;

    return YES;
}

- setupPageLayout:(float)lm :(float)rm :(float)tm :(float)bm 
{
	if (!pageMargin) {
		pageMargin = [PageMargin new];
		[pageMargin setPlpAccessory:plpAccessory];
	}
	[pageMargin setValues:lm right:rm top:tm bottom:bm];
	[pageMargin writePrintInfo];
	return self;
}

/* Application Delegate Methods */

- appWillInit:sender
{
	char temp[MAXPATHLEN+1];
	float lm,rm,tm,bm;

#ifndef DEBUG			/* Don't Dump Core if not Debugging */
	struct rlimit rl={ 0, 0};
    getrlimit( RLIMIT_CORE, &rl);
    rl.rlim_cur=0;
    setrlimit( RLIMIT_CORE, &rl);
#endif

	/* Check and load the application defaults.  The basics are the quit */
	/* behavior and the page margins.  Also sets the menu updating flag. */
	
	if (!NXGetDefaultValue(theAppName,LocalString("SaveAll"))) sprintf(temp,noValue);
	else sprintf(temp,NXGetDefaultValue(theAppName,LocalString("SaveAll")));
	if (!strcmp(temp,yesValue)) saveAll = YES;
	if (!NXGetDefaultValue(theAppName,LocalString("DumpAll"))) sprintf(temp,noValue);
	else sprintf(temp,NXGetDefaultValue(theAppName,LocalString("DumpAll")));
	if (!strcmp(temp,yesValue)) dumpAll = YES;
	
	if (!NXGetDefaultValue(theAppName,LocalString("LeftMargin"))) sprintf(temp,marginDefault);
	else sprintf(temp,NXGetDefaultValue(theAppName,LocalString("LeftMargin")));
	sscanf(temp,"%f",&lm);
	if (!NXGetDefaultValue(theAppName,LocalString("RightMargin"))) sprintf(temp,marginDefault);
	else sprintf(temp,NXGetDefaultValue(theAppName,LocalString("RightMargin")));
	sscanf(temp,"%f",&rm);
	if (!NXGetDefaultValue(theAppName,LocalString("TopMargin"))) sprintf(temp,marginDefault);
	else sprintf(temp,NXGetDefaultValue(theAppName,LocalString("TopMargin")));
	sscanf(temp,"%f",&tm);
	if (!NXGetDefaultValue(theAppName,LocalString("BottomMargin"))) sprintf(temp,marginDefault);
	else sprintf(temp,NXGetDefaultValue(theAppName,LocalString("BottomMargin")));
	sscanf(temp,"%f",&bm);
	
	[self setupPageLayout:lm :rm :tm :bm];
	
	[NXApp setAutoupdate:YES];

    return self;
}

- appDidInit:sender
/*
 * Register our window with the Workspace for icon dropping.
 * Check for files to open specified on the command line.
 * Initialize the menus and check if we were opened to print or quit.
 * If there are no open documents, then open blank documents.
 */
{
    int 		i;
    char 		buffer[MAXPATHLEN+1], temp[MAXPATHLEN+1];
    char 		*directory, *name, *ext;
    u_int		windowNum;
    id 			speaker;
    
	/* register our app icon window with the workspace */
    NXConvertWinNumToGlobal([[NXApp appIcon] windowNum], &windowNum);
    speaker = [NXApp appSpeaker];
    [speaker setSendPort:NXPortFromName(NX_WORKSPACEREQUEST, NULL)];
    [speaker registerWindow:windowNum
    	     toPort:[[NXApp appListener] listenPort]];

	getAppDirectory(launchDir);
	[self setDefaultDir:NXHomeDirectory()];
	
	/* Check for command line files to open */
    if (NXArgc > 1) {
		for (i = 1; i < NXArgc; i++) {
			strcpy(buffer, NXArgv[i]);
			ext = rindex(buffer, '.');
			if (!ext || strcmp(++ext, DocExtension)) {
				strcat(buffer,".");
				strcat(buffer, DocExtension);
			}
			if (*buffer == '/') {
				directory = "/";
				name = buffer;
				name++;
			} else {
				name = rindex(buffer, '/');
				if (name) {
					*name++ = '\0';
					sprintf(temp,"%s/%s",launchDir,buffer);
					directory = temp;
				} else {
					name = buffer;
					directory = NULL;
				}
			}
			haveOpenedDocument = openFile(directory, name, YES) || haveOpenedDocument;
		}
    }

    if (!haveOpenedDocument) [self new:self];	/* if none opened, open one */
    if (NXGetDefaultValue(theAppName,LocalString("Quit"))) {
		[NXApp activateSelf:YES];
		NXPing();
		[NXApp terminate:self];
    }

    initMenu([NXApp mainMenu]);

    return self;
}

- appWillTerminate:sender
/*
 * Overridden to be sure all documents get an opportunity to be saved
 * before exiting the program.  Save the defaults too.
 */
{
    int count, choice;
	float lm,rm,tm,bm;
	char temp[100];
    id window, document, windowList;

    windowList = [NXApp windowList];
	count = [windowList count];
	[pageMargin getValues:&lm right:&rm top:&tm bottom:&bm];
	sprintf(temp,"%f",lm);
	NXWriteDefault([NXApp appName],LocalString("LeftMargin"),temp);
	sprintf(temp,"%f",rm);
	NXWriteDefault([NXApp appName],LocalString("RightMargin"),temp);
	sprintf(temp,"%f",tm);
	NXWriteDefault([NXApp appName],LocalString("TopMargin"),temp);
	sprintf(temp,"%f",bm);
	NXWriteDefault([NXApp appName],LocalString("BottomMargin"),temp);
	if (saveAll) {
		while (count--) {
			document = [[windowList objectAt:count] delegate];
			if ([document respondsTo:@selector(needsSaving)] && [document needsSaving]) {
				[document save:self];
			}
		}
		NXWriteDefault(theAppName,LocalString("SaveAll"),yesValue);
		NXWriteDefault(theAppName,LocalString("DumpAll"),noValue);
	} else if (dumpAll) {
		NXWriteDefault(theAppName,LocalString("DumpAll"),yesValue);
		NXWriteDefault(theAppName,LocalString("SaveAll"),noValue);
	} else {
		NXWriteDefault(theAppName,LocalString("DumpAll"),noValue);
		NXWriteDefault(theAppName,LocalString("SaveAll"),noValue);
		while (count--) {
			window = [windowList objectAt:count];
			document = [window delegate];
			if ([document respondsTo:@selector(needsSaving)] && [document needsSaving]) {
				choice = NXRunAlertPanel(LocalString("Quit"),
										 LocalString("You have unsaved documents."),
										 LocalString("Review Unsaved"),
										 LocalString("Quit Anyway"),
										 LocalString("Cancel"));
				if (choice == NX_ALERTOTHER) return nil;
				else if (choice == NX_ALERTALTERNATE) return self;
				else if (choice == NX_ALERTDEFAULT) {
					count = 0;
					choice = [windowList count];
					while (choice--) {
						window = [windowList objectAt:choice];
						document = [window delegate];
						if ([document respondsTo:@selector(windowWillClose:)]) {
							if ([document windowWillClose:self]) [window close];
							else return nil;
						}
					}
				}
	    	}
		}
    }

    return self;
}
    
- (int)appOpenFile:(const char *)path type:(const char *)type
/*
 * This method is performed whenever a user double-clicks on an icon
 * in the Workspace Manager representing a app program document.
 */
{
    char *name;
    char directory[MAXPATHLEN+1];

    if (type && !strcmp(type, DocExtension)) {
		strcpy(directory, path);
		name = rindex(directory, '/');
		if (name) {
#ifdef DEBUG
			printf("Directory %s: File %s\n",directory,name);
#endif
			if (name != index(directory, '/')) {
				*name++ = '\0';
				if (openFile(directory, name, YES)) {
					haveOpenedDocument = YES;
					return YES;
				}
			} else {
				name++;
				if (openFile("/", name, YES)) {
					haveOpenedDocument = YES;
					return YES;
				}
			}
#ifdef DEBUG
			printf("After Open, File is %s\n",name);
#endif
		}
    }

    return NO;
}

- (BOOL)appAcceptsAnotherFile:sender
/*
 * We accept any number of appOpenFile:type: messages.
 */
{
    return YES;
}

- (int)app:sender unmounting:(const char *)fullPath
{
	id	windowList	= [NXApp windowList];
	int	count		= [windowList count];
    id	document	= NULL;
	
	/* if any of our documents are in that path, close them and notify the user */

    while (count--) {
		document = documentInWindow([windowList objectAt:count]);
		if (document && (!strncmp([document directory],fullPath,strlen(fullPath)))) {
			Notify(fullPath,LocalString("is being unmounted; documents here will close."));
		/* Use performClose so that the window delegate will be notified. */
			[[document window] performClose:self];
		}
		
    }
	/* the current directory is wherever our current document is, so we're all set */
    return 0;
}		


/* Listener/Speaker remote methods */

// IconDragging Stuff, oh boy, oh boy!
static char **filePath = NULL;
static int files = 0;

- (int)iconEntered:(int)windowNum at:(double)x :(double)y
    iconWindow:(int)iconWindowNum iconX:(double)iconX iconY:(double)iconY
    iconWidth:(double)iconWidth iconHeight:(double)iconHeight
    pathList:(char *)pathList
{
	char *stringPos, *tempPtr;
	char tempBuf[MAXPATHLEN+1];
	int count = 0;
	
	if (filePath) {
		freeList(filePath);
		filePath = NULL;
	}
	stringPos = tempPtr = pathList;
	files = 0;
	    
	/* This code just parses the pathList for the filenames and explictly */
	/* null-terminates them after copying into a buffer.  It appends that */
	/* buffer to the dynamically-allocated filelist which keeps track of it */

	/* the number of tabs + 1 equals the number of files dragged in */

	while (stringPos = index(stringPos, '\t')) {
		count = (int)(stringPos-tempPtr);
		strncpy(tempBuf,tempPtr,count);
		*(tempBuf+count) = '\0';
		filePath = addFile(tempBuf,filePath,files,MyZone);
		files++;
		stringPos++;
		tempPtr=stringPos;
    }
	strncpy(tempBuf,tempPtr,strlen(tempPtr));
	*(tempBuf+strlen(tempPtr)) = '\0';
	filePath = addFile(tempBuf,filePath,files,MyZone);
	files++;

    return 0;
}

- (int)iconExitedAt:(double)x :(double)y
// Throw away the files that were just waved in our faces
{
	freeList(filePath);
	filePath = NULL;
	files = 0;
	return 0;
}

- (int)iconReleasedAt:(double)x :(double)y ok:(int *)flag
// try to open the files in FileList
{	
	char *strPtr;
	
	/* accept the icon */
    *flag = 1;
   
    NX_DURING
	while (files--) {
		strPtr = rindex(filePath[files],'/');
		if (strPtr != index(filePath[files],'/')) {
			*strPtr++ = '\0';
			openFile(filePath[files],strPtr,YES);
		} else {
			strPtr++;
			openFile("/",strPtr,YES);
		}
	}
    NX_HANDLER
    NX_ENDHANDLER
		
    return 0;
}

// Filename and path methods
- (int)msgDirectory:(const char **)fullPath ok:(int *)flag
{
    *fullPath = [self currentDirectory];
    if (*fullPath) *flag = YES;
    else {
		*fullPath = "";
		*flag = NO;
    }
    return 0;
}

- (int)msgFile:(const char **)fullPath ok:(int *)flag
{
    const char *file;

    file = [[self currentDocument] fileName];
    if (file) {
		*fullPath = file;
		*flag = YES;
    } else {
		*fullPath = "";
		*flag = NO;
    }

    return 0;
}

// Miscellaneous messages we might want to handle
- (int)msgPrint:(const char *)fullPath ok:(int *)flag
{
    BOOL close;
    id document = nil;
    char *directory, *name;
    char path[MAXPATHLEN+1];
    char buffer[MAXPATHLEN+1];

    InMsgPrint = YES;
    strcpy(buffer, fullPath);
    name = rindex(buffer, '/');
    if (name) {
		*name++ = '\0';
		directory = buffer;
    } else {
		name = buffer;
		directory = NULL;
    }
    if (!chdir(directory) && getwd(path)) {
		strcat(path, "/");
		strcat(path, name);
		document = [findDocument(path) delegate];
    }
    if (document) close = NO;
    else {
		document = openFile(directory, name, NO);
		if (document) haveOpenedDocument = YES;
		close = YES;
    }
    if (document && ![[document view] isEmpty]) {
		[NXApp setPrintInfo:[document printInfo]];
		[[document view] printPSCode:self];
		if (close) [[[document view] window] close];
		*flag = YES;
    } else *flag = NO;
    InMsgPrint = NO;

    return 0;
}

- (int)msgVersion:(const char **)aString ok:(int *)flag
{
    char buf[20];

    sprintf(buf, VersionFormat, theVersion);
    *aString = NXCopyStringBuffer(buf);
    *flag = YES;

    return 0;
}

- (int)msgQuit:(int *)flag
{
    *flag = ([NXApp terminate:self] ? NO : YES);
    return 0;
}

@end

